package rtcm

import (
	"dgo/goutils/gnss"
	"fmt"
	"gitee.com/ymofen/golog"
	"gitee.com/ymofen/sputils"
	"sort"
	"strings"
	"sync/atomic"
)

type SignalChecker struct {
	log       golog.Logger
	caption   string
	epochDec  *gnss.GnssSVEpochRtcmDecoder
	sub       *sputils.SpBaseSub
	signalMap SignalMap

	freqNameCheckConf string
	signalCheckConf   string
	epochN            atomic.Int32
	compareFailN      atomic.Int32
}

type sysSignal struct {
	rnxCode      []string
	rtcmSignalId []string
	freqName     []string
}

type SignalMap map[string]*sysSignal

func (this SignalMap) checkSys(sys string) *sysSignal {
	if this[sys] == nil {
		this[sys] = &sysSignal{}
	}
	return this[sys]
}

func (this SignalMap) sortSignal() {
	for _, v := range this {
		sort.Slice(v.rnxCode, func(i, j int) bool {
			return v.rnxCode[i] < v.rnxCode[j]
		})

		sort.Slice(v.rtcmSignalId, func(i, j int) bool {
			return v.rnxCode[i] < v.rnxCode[j]
		})
	}

}

func (this SignalMap) SignalText(sys string, sep string) string {
	ss := this[sys]
	if ss == nil {
		return ""
	}

	return strings.Join(ss.rnxCode, sep)
}

func (this SignalMap) AllSignalText() string {
	var lst []string
	for k, v := range this {
		lst = append(lst, fmt.Sprintf("%s=%s", k, strings.Join(v.rnxCode, ",")))
	}
	sort.Strings(lst)
	return strings.Join(lst, "&")
}

func (this SignalMap) AllSignalRtcmID() string {
	var lst []string
	for k, v := range this {
		lst = append(lst, fmt.Sprintf("%s=%s", k, strings.Join(v.rtcmSignalId, ",")))
	}
	sort.Strings(lst)
	return strings.Join(lst, "&")
}

func (this SignalMap) AllFreqName() string {
	var lst []string
	for k, v := range this {
		lst = append(lst, fmt.Sprintf("%s=%s", k, strings.Join(v.freqName, ",")))
	}
	sort.Strings(lst)
	return strings.Join(lst, "&")
}

func NewSignalChecker() *SignalChecker {
	rval := &SignalChecker{}
	rval.sub = sputils.NewSpBaseSub("SignalChecker")
	rval.log = golog.Default()
	rval.Init()
	return rval
}

func (this *SignalChecker) SetLogger(logger golog.Logger) {
	this.log = logger
}

// C2=B1I,B2I
func (this *SignalChecker) UpdateCheckFreqNameConf(s string) {
	this.freqNameCheckConf = s
}

// C2=B1I,B2I
func (this *SignalChecker) UpdateCheckSignalConf(s string) {
	this.signalCheckConf = s
}

func (this *SignalChecker) compareFreqNameConf() error {
	conf := this.freqNameCheckConf
	if len(conf) == 0 {
		return nil
	}

	freqNames := this.signalMap.AllFreqName()
	if freqNames != conf {
		return fmt.Errorf("freq compare fail %s!=%s", conf, freqNames)
	}
	return nil

}

func (this *SignalChecker) compareSignalConf() error {
	conf := this.signalCheckConf
	if len(conf) == 0 {
		return nil
	}

	str := this.signalMap.AllSignalText()
	if str != conf {
		return fmt.Errorf("signal compare fail %s!=%s", conf, str)
	}
	return nil

}

func (this *SignalChecker) GetEpochNum() int {
	return int(this.epochN.Load())
}

func (this *SignalChecker) GetStatus() string {
	return fmt.Sprintf("epochN:%d, compareFailN:%d", this.epochN.Load(), this.compareFailN.Load())
}

func (this *SignalChecker) Init() {
	this.signalMap = make(map[string]*sysSignal)
	this.epochDec = gnss.NewGnssSVEpochRtcmDecoder()
	this.epochDec.OnEpochFunc = func(epoch *gnss.GnssSVEpoch, checktype int) {
		this.epochN.Add(1)
		signalMap := make(SignalMap)
		epoch.StatSignalEx(func(navisys byte, singaltext string, singalstr string, signalid byte, flag byte) {
			rtcmSignalId := gnss.GnssSvSigId2RtcmSigID(navisys, signalid)
			var ss *sysSignal
			if navisys == gnss.SYS_BD {
				if flag == 2 {
					ss = signalMap.checkSys("C2")
				} else if flag == 3 {
					ss = signalMap.checkSys("C3")
				}
			} else {
				ss = signalMap.checkSys(gnss.GetNavSysCode(navisys))
			}
			if ss != nil {
				ss.rnxCode = append(ss.rnxCode, singalstr)
				ss.rtcmSignalId = append(ss.rtcmSignalId, fmt.Sprintf("%d", rtcmSignalId))
				ss.freqName = append(ss.freqName, FreqBandFromSignalRtcmId(navisys, rtcmSignalId))
			}
		})
		signalMap.sortSignal()
		this.signalMap = signalMap
		err := this.compareFreqNameConf()
		if err != nil {
			this.log.Warnf("%s", err.Error())
			this.compareFailN.Add(1)
		}

		err = this.compareSignalConf()
		if err != nil {
			this.log.Warnf("%s", err.Error())
			this.compareFailN.Add(1)
		}
	}
}

func (this *SignalChecker) InputBuffer(buf []byte) {
	this.epochDec.InputBuf(buf)
}

func (this *SignalChecker) Close() error {
	sub := this.sub
	if sub != nil {
		return sub.Close()
	}
	return nil
}

func (this *SignalChecker) UpdateConf(subconf string) {
	err := this.sub.ReloadConfString(subconf, func(id, topic string, args ...interface{}) (ok bool) {
		action := args[0].(int)
		if action == sputils.SpTypeData {
			buf := args[1].([]byte)
			this.epochDec.InputBuf(buf)
		} else if action == sputils.SpTypeConnectFail {
			this.log.Warnf("connect fail %v", args[1])
		} else if action == sputils.SpTypeConnected {
			this.log.Infof("connected %v %v", args[1], args[2])
		}
		return true
	})
	if err != nil {
		this.log.Warnf("updateconf fail:%s", err.Error())
	}
}
